﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace Communicator
{
    public class Client
    {
        #region Fields
        public static int BufferSize = 1024 * 1024;

        public string ServerIP { get; set; }
        public int ServerPort { get; set; }

        protected Socket _client;

        public bool Connected
        {
            get { return _client.Connected; }
        }

        protected byte[] _buffer = new byte[BufferSize];

        protected string _messageToSend;

        protected StringBuilder _dataBuilder = new StringBuilder();

        protected ManualResetEvent _waitConnected = new ManualResetEvent(false);

        protected Logger _log;

        public FpEventHandle<bool> FpConnected { get; set; }
        public FpEventHandle<string> FpDisconnected { get; set; }
        public FpEventHandle<string> FpRecv { get; set; }
        public FpEventHandle<string> FpSend { get; set; }

        #endregion

        #region Constructor
        public Client() 
        {
            FpConnected = (x, y) => { };
            FpDisconnected = (x, y) => { };
            FpRecv = (x, y) => { };
            FpSend = (x, y) => { };
        }

        public Client(string serverIp, int serverPort)
        {
            ServerIP = serverIp;
            ServerPort = serverPort;

            FpConnected = (x, y) => { };
            FpDisconnected = (x, y) => { };
            FpRecv = (x, y) => { };
            FpSend = (x, y) => { };
        }

        public Client(string serverAddress)
        {
            ServerIP = serverAddress.Split(':')[0];
            ServerPort = int.Parse(serverAddress.Split(':')[1]);
        }
        #endregion

        #region Connect Disconnect
        public void Connect()
        {
            _log = new Logger("client", "Log/Communicator");
            try
            {
                _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _client.BeginConnect(ServerIP, ServerPort, OnConnect, _client);
                _waitConnected.WaitOne();
            }
            catch (Exception ex) { _log.WriteLog("Cannot connect to {0}:{1}. ERROR: {2}", ServerIP, ServerPort, ex.StackTrace); }
        }

        public void Disconnect()
        {
            if (Connected)
            {
                if (_client != null) _client.Close();
            }
            FpDisconnected(this, null);
        }
        #endregion

        #region Callback functions
        protected void OnConnect(IAsyncResult result)
        {
            Socket socket = (Socket)result.AsyncState;
            try
            {
                _waitConnected.Set();
                socket.EndConnect(result);
                socket.BeginReceive(_buffer, 0, BufferSize, SocketFlags.None, ReceiveCallback, socket);
                _log.WriteLog("Connected to {0}:{1} !", ServerIP, ServerPort);
            }
            catch (Exception ex) { _log.WriteLog("Cannot connect to {0}:{1}. ERROR: {2}", ServerIP, ServerPort, ex.StackTrace); }
            FpConnected(this, Connected);
        }

        protected void ReceiveCallback(IAsyncResult result)
        {
            Socket socket = (Socket)result.AsyncState;
            try
            {
                int nReceive = socket.EndReceive(result);
                
                if (nReceive > 0)
                {
                    _dataBuilder.Append(Encoding.UTF8.GetString(_buffer, 0, nReceive));
                    string content = _dataBuilder.ToString();
                    if (content.Contains("\0"))
                    {
                        FpRecv(this, content);
                        _log.WriteLog("Received: " + content);
                    }
                }
                socket.BeginReceive(_buffer, 0, BufferSize, SocketFlags.None, ReceiveCallback, socket);
            }
            catch (Exception ex) { _log.WriteLog("Received failed. ERROR: {0}", ex.StackTrace); }
        }

        protected void SendCallback(IAsyncResult result)
        {
            Socket socket = (Socket)result.AsyncState;
            try
            {
                int len = socket.EndSend(result);
                FpSend(this, _messageToSend);
                _log.WriteLog("Send '{2}' to {0}:{1} successfully !", ServerIP, ServerPort, _messageToSend);
            }
            catch (Exception ex) { _log.WriteLog("Send '{3}' to {0}:{1} error: {2}", ServerIP, ServerPort, ex.StackTrace, _messageToSend); }
        }
        #endregion

        #region Send
        public void Send(string mess)
        {
            _messageToSend = mess;
            byte[] buffer = Encoding.UTF8.GetBytes(mess + "\r\n\r\n\0");
            try
            {
                _client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallback, _client);
            }
            catch (Exception ex) { _log.WriteLog("Send message '{0}' to server {1}:{2} failed. ERROR: {3}", mess, ServerIP, ServerPort, ex.StackTrace); }
        }
        #endregion
    }

    public delegate string FpGetUsername();
}
